package com.ihr360.payroll.service.salarytasklet.expr.engine;

import com.ihr360.expr.ExprBigDecimal;
import com.ihr360.expr.ExprDouble;
import com.ihr360.expr.ExprError;
import com.ihr360.expr.ExprException;
import com.ihr360.expr.ExprMissing;
import com.ihr360.expr.context.IEvaluationContext;
import com.ihr360.expr.core.Expr;
import com.ihr360.expr.core.ExprFunction;
import com.ihr360.expr.core.ExprType;
import com.ihr360.expr.core.ExprVariable;
import com.ihr360.expr.data.ExprValueMeta;
import com.ihr360.expr.engine.EngineProvider;
import com.ihr360.expr.engine.VariableFetcher;
import com.ihr360.expr.engine.VariableID;
import com.ihr360.expr.function.ExcelFunctionProvider;
import com.ihr360.expr.function.FunctionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * 薪资表达式计算引擎
 * @author davidwei
 */
public class SalaryExprEngineProvider implements EngineProvider {
    Logger logger = LoggerFactory.getLogger(SalaryExprEngineProvider.class.getName());
    private FunctionManager functions = new FunctionManager();

    /**
     * 保存表达式求值后的结果
     */
    protected Map<VariableID, Expr> values = new HashMap();

    /**
     * 保存引用的服务的变量
     */
    private Map<String, Expr> variables = new HashMap<>();

    /**
     * 保存分段计算过程中后分段变量
     */
    private Map<String, Expr> phasedVariables = new HashMap<>();
    private  VariableFetcher personalVariableFetcher = null;

    public SalaryExprEngineProvider() {
        functions.add(new ExcelFunctionProvider());
    }

    public SalaryExprEngineProvider(VariableFetcher variableFetcher) {
        functions.add(new ExcelFunctionProvider());
        this.personalVariableFetcher = variableFetcher;
    }

    @Override
    public void setVariableFetcher(VariableFetcher personalVariableFetcher) {
        this.personalVariableFetcher = personalVariableFetcher;
    }

    @Override
    public void addVariable(String variableId, Expr expr) {
        if (VariableID.hasSheet(variableId)) {
            this.phasedVariables.put(variableId, expr);
            return;
        }
        this.variables.put(variableId, expr);
    }

    @Override
    public void removeVariable(String variableId) {
        this.variables.remove(variableId);
        this.values.remove(variableId);
    }

    @Override
    public Expr getVariable(VariableID variable) throws ExprException {
        if (logger.isDebugEnabled()) {
            logger.debug("get variable=" + variable.getVariableId());
        }
        String name = variable.getVariableId();

        if (variable.hasSheet()) {
            Expr value= phasedVariables.get(name);
            if (value!=null){
                return value;
            }
        }

        Expr e = variables.get(name);
        if (e == null) {
            if (this.personalVariableFetcher != null) {
                e = this.personalVariableFetcher.variable(variable.getVariableId());
            }
            if (e != null) {
                return e;
            }
            return ExprError.NAME;
        }

        return e;
    }

    @Override
    public void inputChanged(VariableID variableId, Expr input) {
        if (logger.isDebugEnabled()) {
            logger.debug("Input changed: {} =\n {}", variableId, input);
        }
        if (VariableID.hasSheet(variableId.getVariableId())) {
            this.phasedVariables.put(variableId.getVariableId(), input);
            return;
        }
        this.variables.put(variableId.getVariableId(), input);
    }

    @Override
    public void valueChanged(VariableID variableID, Expr value) {
        if (logger.isDebugEnabled()) {
            logger.debug("Value changed: {} =\n {}", variableID, value);
        }
        //此时应该记录变量的更新值，这样在进行取值的时候可以直接返回值，不需要每次重复计算
        if (variableID.getSheet() != null) {
            phasedVariables.put(variableID.getVariableId(), value);
        } else {
            if (variableID.hasNamespece()) {
                variables.put(variableID.getVariableId(), value);
            }
        }
    }

    @Override
    public void onError(VariableID variable, Expr input, ExprError e) {
        if (logger.isErrorEnabled()) {
            logger.debug("onError {} {}; {}", variable.getVariableId(), e.toString(), input.toString());
        }
        putValue(variable, ExprError.NA);
        personalVariableFetcher.onError(variable,input,e,0);
    }

    @Override
    public void validate(ExprVariable variable) throws ExprException {
        if (logger.isDebugEnabled()) {
            logger.debug("validator {} doesn't have variableId\n {}", variable.getName());
        }
    }

    @Override
    public boolean isValueKeeper() {
        return true;
    }

    @Override
    public Expr getValue(VariableID variableID) {
        return this.values.get(variableID);
    }

    @Override
    public void putValue(VariableID variableID, Expr expr) {
        if (expr==null){
            logger.warn("EEEEEEEEEE "+variableID.getVariableId()+ " expression is null");
        }
        if (this.personalVariableFetcher != null && expr != null) {
            if (ExprType.Double.equals(expr.type) || ExprType.BigDecimal.equals(expr.type)) {
                ExprValueMeta exprValueMeta = this.personalVariableFetcher.valueMeta(variableID.getVariableId());
                if (exprValueMeta != null) {
                    if (ExprType.Double.equals(expr.type)) {
                        ExprDouble exprDouble = (ExprDouble) expr;
                        expr = new ExprDouble(BigDecimal.valueOf(exprDouble.doubleValue()).setScale(exprValueMeta.getScale(), exprValueMeta.getRoundingMode()).doubleValue());
                    } else if (ExprType.BigDecimal.equals(expr.type)) {
                        ExprBigDecimal exprDouble = (ExprBigDecimal) expr;
                        expr = new ExprBigDecimal(BigDecimal.valueOf(exprDouble.doubleValue()).setScale(exprValueMeta.getScale(), exprValueMeta.getRoundingMode()));
                    }
                }
            }
        }
        this.values.put(variableID, expr);
    }


    @Override
    public void removeValue(VariableID variableID) {
        this.values.remove(variableID);
    }

    @Override
    public Expr evaluateFunction(IEvaluationContext context,
                                 ExprFunction function) throws ExprException {
        return functions.evaluate(context, function);
    }

    @Override
    public Expr evaluateVariable(IEvaluationContext context,
                                 ExprVariable variable) throws ExprException {
        if (logger.isDebugEnabled()) {
            logger.debug("provider variable=" + variable.getName());
        }

        String name = variable.getName();

        if (VariableID.hasSheet(name)) {
            Expr value= phasedVariables.get(name);
            if (value!=null){
                return value;
            }
        }

        Expr e = variables.get(name);

        if (e == null) {
            if (this.personalVariableFetcher != null) {
                e = this.personalVariableFetcher.variable(variable.getName());
            }
            if (e != null) {
                return e;
            }
            //此时是否需要记录？？？？
            //return ExprError.NAME;
        }

        return e;
    }

    @Override
    public Expr notifyMissing(ExprVariable variable) {
        if (this.personalVariableFetcher != null) {
            return this.personalVariableFetcher.notifyMissing(variable);
        }
        return ExprMissing.MISSING;
    }

    @Override
    public void clear() {
        if (this.values!=null){
            this.values.clear();
        }
        if (this.variables!=null){
            this.variables.clear();
        }
        if (this.phasedVariables!=null){
            this.phasedVariables.clear();
        }
    }

    public Map<VariableID, Expr> getValues() {
        return values;
    }

    public Map<String, Expr> getVariables() {
        return variables;
    }

    public Map<String, Expr> getPhasedVariables() {
        return phasedVariables;
    }
}